/*!
 * \file SelMocker.cpp
 * \project WonderTrader
 *
 * \author Wesley
 * \date 2020/03/30
 *
 * \brief SEL策略模拟器实现文件
 * \details 本文件实现了SelMocker类的各种方法，用于在WonderTrader回测框架中模拟选股策略的执行。
 *          主要实现功能包括：
 *          - 选股策略的生命周期管理
 *          - 持仓信息和交易信号管理
 *          - 历史数据获取和K线数据访问
 *          - 交易日志和资金变化记录
 *          - 策略上下文和回调事件处理
 *          - 动态利润计算和风险控制
 */
#include "SelMocker.h"
#include "WtHelper.h"

#include <exception>
#include <boost/filesystem.hpp>

#include "../Share/StdUtils.hpp"
#include "../Share/StrUtil.hpp"
#include "../Includes/WTSContractInfo.hpp"
#include "../Includes/WTSSessionInfo.hpp"
#include "../Share/decimal.h"
#include "../Includes/WTSVariant.hpp"

#include "../WTSTools/WTSLogger.h"

/**
 * @brief 生成SEL策略上下文ID
 * @return 唯一的上下文ID
 * @details 使用原子操作确保线程安全，从3000开始递增分配ID
 */
inline uint32_t makeSelCtxId()
{
	static std::atomic<uint32_t> _auto_context_id{ 3000 };
	return _auto_context_id.fetch_add(1);
}

/**
 * @brief SelMocker构造函数
 * @param replayer 历史数据回放器指针
 * @param name 策略名称
 * @param slippage 滑点（基点）
 * @details 初始化SEL策略模拟器对象，设置回放器引用和滑点参数。
 *          构造函数将所有统计变量初始化为默认值，为选股策略模拟做准备。
 */
SelMocker::SelMocker(HisDataReplayer* replayer, const char* name, int32_t slippage /* = 0 */)
	: ISelStraCtx(name)
	, _replayer(replayer)
	, _total_calc_time(0)
	, _emit_times(0)
	, _is_in_schedule(false)
	, _ud_modified(false)
	, _strategy(NULL)
	, _slippage(slippage)
{
	_context_id = makeSelCtxId();
}


/**
 * @brief SelMocker析构函数
 * @details 清理SEL策略模拟器资源。当前实现为空，
 *          所有资源清理工作在其他地方完成。
 */
SelMocker::~SelMocker()
{
}

/**
 * @brief 导出输出文件
 * @details 将策略运行过程中的各种数据导出到CSV文件，包括：
 *          - trades.csv: 交易记录
 *          - closes.csv: 平仓记录
 *          - funds.csv: 资金变化记录
 *          - signals.csv: 信号记录
 *          
 *          输出目录结构：输出根目录/策略名称/文件名.csv
 */
void SelMocker::dump_outputs()
{
	std::string folder = WtHelper::getOutputDir();
	folder += _name;
	folder += "/";
	boost::filesystem::create_directories(folder.c_str());

	std::string filename = folder + "trades.csv";
	std::string content = "code,time,direct,action,price,qty,tag,fee\n";
	content += _trade_logs.str();
	StdFile::write_file_content(filename.c_str(), (void*)content.c_str(), content.size());

	filename = folder + "closes.csv";
	content = "code,direct,opentime,openprice,closetime,closeprice,qty,profit,totalprofit,entertag,exittag\n";
	content += _close_logs.str();
	StdFile::write_file_content(filename.c_str(), (void*)content.c_str(), content.size());


	filename = folder + "funds.csv";
	content = "date,closeprofit,positionprofit,dynbalance,fee\n";
	content += _fund_logs.str();
	StdFile::write_file_content(filename.c_str(), (void*)content.c_str(), content.size());


	filename = folder + "signals.csv";
	content = "code,target,sigprice,gentime,usertag\n";
	content += _sig_logs.str();
	StdFile::write_file_content(filename.c_str(), (void*)content.c_str(), content.size());
}

/**
 * @brief 记录选股信号
 * @param stdCode 标准合约代码
 * @param target 目标仓位
 * @param price 信号价格
 * @param gentime 生成时间
 * @param usertag 用户标签
 * @details 将选股信号记录到内存缓冲区，稍后写入signals.csv文件
 */
void SelMocker::log_signal(const char* stdCode, double target, double price, uint64_t gentime, const char* usertag /* = "" */)
{
	_sig_logs << stdCode << "," << target << "," << price << "," << gentime << "," << usertag << "\n";
}

/**
 * @brief 记录交易日志
 * @param stdCode 标准合约代码
 * @param isLong 是否做多
 * @param isOpen 是否开仓
 * @param curTime 当前时间
 * @param price 交易价格
 * @param qty 交易数量
 * @param userTag 用户标签
 * @param fee 手续费
 * @details 将交易记录到内存缓冲区，稍后写入trades.csv文件
 */
void SelMocker::log_trade(const char* stdCode, bool isLong, bool isOpen, uint64_t curTime, double price, double qty, const char* userTag, double fee)
{
	_trade_logs << stdCode << "," << curTime << "," << (isLong ? "LONG" : "SHORT") << "," << (isOpen ? "OPEN" : "CLOSE") << "," << price << "," << qty << "," << userTag << "," << fee << "\n";
}

/**
 * @brief 记录平仓日志
 * @param stdCode 标准合约代码
 * @param isLong 是否做多
 * @param openTime 开仓时间
 * @param openpx 开仓价格
 * @param closeTime 平仓时间
 * @param closepx 平仓价格
 * @param qty 交易数量
 * @param profit 本次利润
 * @param totalprofit 总利润
 * @param enterTag 进场标签
 * @param exitTag 出场标签
 * @details 将平仓记录到内存缓冲区，稍后写入closes.csv文件
 */
void SelMocker::log_close(const char* stdCode, bool isLong, uint64_t openTime, double openpx, uint64_t closeTime, double closepx, double qty,
	double profit, double totalprofit /* = 0 */, const char* enterTag /* = "" */, const char* exitTag /* = "" */)
{
	_close_logs << stdCode << "," << (isLong ? "LONG" : "SHORT") << "," << openTime << "," << openpx
		<< "," << closeTime << "," << closepx << "," << qty << "," << profit << ","
		<< totalprofit << "," << enterTag << "," << exitTag << "\n";
}

/**
 * @brief 初始化SEL策略工厂
 * @param cfg 配置参数
 * @return 初始化是否成功
 * @details 根据配置参数初始化SEL策略工厂和策略实例。主要完成：
 *          1. 动态加载策略工厂模块
 *          2. 获取工厂创建和删除函数
 *          3. 创建策略工厂实例
 *          4. 根据配置创建具体策略实例
 *          5. 初始化策略参数
 */
bool SelMocker::init_sel_factory(WTSVariant* cfg)
{
	if (cfg == NULL)
		return false;

	const char* module = cfg->getCString("module");

	DllHandle hInst = DLLHelper::load_library(module);
	if (hInst == NULL)
		return false;

	FuncCreateSelStraFact creator = (FuncCreateSelStraFact)DLLHelper::get_symbol(hInst, "createSelStrategyFact");
	if (creator == NULL)
	{
		DLLHelper::free_library(hInst);
		return false;
	}

	_factory._module_inst = hInst;
	_factory._module_path = module;
	_factory._creator = creator;
	_factory._remover = (FuncDeleteSelStraFact)DLLHelper::get_symbol(hInst, "deleteSelStrategyFact");
	_factory._fact = _factory._creator();

	WTSVariant* cfgStra = cfg->get("strategy");
	if (cfgStra)
	{
		_strategy = _factory._fact->createStrategy(cfgStra->getCString("name"), cfgStra->getCString("id"));
		if (_strategy)
		{
			WTSLogger::info("Strategy %s.%s created,strategy ID: %s", _factory._fact->getName(), _strategy->getName(), _strategy->id());
		}
		_strategy->init(cfgStra->get("params"));
		_name = _strategy->id();
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
//IDataSink接口实现 - 数据接收器接口

/**
 * @brief 处理初始化事件
 * @details 策略模拟器初始化完成后的回调处理，
 *          触发策略的on_init()方法
 */
void SelMocker::handle_init()
{
	this->on_init();
}

/**
 * @brief 处理K线收盘事件
 * @param stdCode 标准合约代码
 * @param period K线周期
 * @param times 周期倍数
 * @param newBar 新的K线数据
 * @details 处理K线收盘事件，触发策略的on_bar()方法
 */
void SelMocker::handle_bar_close(const char* stdCode, const char* period, uint32_t times, WTSBarStruct* newBar)
{
	this->on_bar(stdCode, period, times, newBar);
}

/**
 * @brief 处理调度事件
 * @param uDate 日期
 * @param uTime 时间
 * @details 处理定时调度事件，计算下一分钟时间并触发on_schedule()方法
 */
void SelMocker::handle_schedule(uint32_t uDate, uint32_t uTime)
{
	uint32_t nextTime = TimeUtils::getNextMinute(uTime, 1);
	if (nextTime < uTime)
		uDate = TimeUtils::getNextDate(uDate);
	this->on_schedule(uDate, uTime, nextTime);
}

/**
 * @brief 交易时段开始回调
 * @param curTDate 当前交易日期
 * @details 处理交易时段开始事件，主要功能：
 *          1. 释放所有冻结持仓
 *          2. 为下一个交易日做准备
 */
void SelMocker::on_session_begin(uint32_t curTDate)
{
	//每个交易时段开始都要把冻结持仓释放
	for (auto& it : _pos_map)
	{
		const char* stdCode = it.first.c_str();
		PosInfo& pInfo = (PosInfo&)it.second;
		if (!decimal::eq(pInfo._frozen, 0))
		{
			log_debug("%.0f of %s frozen released on %u", pInfo._frozen, stdCode, curTDate);
			pInfo._frozen = 0;
		}
	}
}

/**
 * @brief 处理交易时段结束事件
 * @param uCurDate 当前交易日期
 * @details 处理交易时段结束事件，触发策略的on_session_end()方法
 */
void SelMocker::handle_session_end(uint32_t uCurDate)
{
	this->on_session_end(uCurDate);
}

/**
 * @brief 处理回放完成事件
 * @details 历史数据回放完成后的处理，包括：
 *          1. 输出策略执行统计信息
 *          2. 导出各类数据文件
 *          3. 触发策略的on_bactest_end()方法
 */
void SelMocker::handle_replay_done()
{
	WTSLogger::log_dyn_f("strategy", _name.c_str(), LL_INFO, 
		"Strategy has been scheduled for {} times,totally taking {} microsecs,average of {} microsecs",
		_emit_times, _total_calc_time, _total_calc_time / _emit_times);

	dump_outputs();

	this->on_bactest_end();
}

/**
 * @brief 处理Tick数据事件
 * @param stdCode 标准合约代码
 * @param curTick 当前Tick数据
 * @details 处理新的Tick数据到达事件，触发策略的on_tick()方法
 */
void SelMocker::handle_tick(const char* stdCode, WTSTickData* curTick)
{
	this->on_tick(stdCode, curTick, true);
}


//////////////////////////////////////////////////////////////////////////
//回调函数实现

/**
 * @brief K线收盘回调
 * @param stdCode 标准合约代码
 * @param period K线周期
 * @param times 周期倍数
 * @param newBar 新的K线数据
 * @details 处理K线收盘事件，更新K线标签状态。
 *          将周期信息标准化并标记为已收盘状态。
 */
void SelMocker::on_bar(const char* stdCode, const char* period, uint32_t times, WTSBarStruct* newBar)
{
	if (newBar == NULL)
		return;

	std::string realPeriod;
	if (period[0] == 'd')
		realPeriod = StrUtil::printf("%s%u", period, times);
	else
		realPeriod = StrUtil::printf("m%u", times);

	std::string key = StrUtil::printf("%s#%s", stdCode, realPeriod.c_str());
	KlineTag& tag = _kline_tags[key];
	tag._closed = true;

	on_bar_close(stdCode, realPeriod.c_str(), newBar);
}

/**
 * @brief 策略初始化回调
 * @details 策略模拟器初始化完成后的处理，主要完成：
 *          1. 调用具体策略的初始化方法
 *          2. 输出策略初始化信息，包括滑点设置
 */
void SelMocker::on_init()
{
	if (_strategy)
		_strategy->on_init(this);

	WTSLogger::info("SEL Strategy initialized, with slippage: %d", _slippage);
}

/**
 * @brief 更新动态利润
 * @param stdCode 标准合约代码
 * @param price 当前价格
 * @details 根据当前价格更新指定合约的动态利润。主要功能：
 *          1. 计算每个持仓明细的浮动盈亏
 *          2. 更新最大盈利和最大亏损记录
 *          3. 汇总总的动态利润
 *          4. 更新资金信息中的总动态利润
 */
void SelMocker::update_dyn_profit(const char* stdCode, double price)
{
	auto it = _pos_map.find(stdCode);
	if (it != _pos_map.end())
	{
		PosInfo& pInfo = (PosInfo&)it->second;
		if (pInfo._volume == 0)
		{
			pInfo._dynprofit = 0;
		}
		else
		{
			WTSCommodityInfo* commInfo = _replayer->get_commodity_info(stdCode);
			double dynprofit = 0;
			for (auto pit = pInfo._details.begin(); pit != pInfo._details.end(); pit++)
			{
				DetailInfo& dInfo = *pit;
				dInfo._profit = dInfo._volume*(price - dInfo._price)*commInfo->getVolScale()*(dInfo._long ? 1 : -1);
				if (dInfo._profit > 0)
					dInfo._max_profit = max(dInfo._profit, dInfo._max_profit);
				else if (dInfo._profit < 0)
					dInfo._max_loss = min(dInfo._profit, dInfo._max_loss);

				dynprofit += dInfo._profit;
			}

			pInfo._dynprofit = dynprofit;
		}
	}

	double total_dynprofit = 0;
	for (auto& v : _pos_map)
	{
		const PosInfo& pInfo = v.second;
		total_dynprofit += pInfo._dynprofit;
	}

	_fund_info._total_dynprofit = total_dynprofit;
}

/**
 * @brief Tick数据回调
 * @param stdCode 标准合约代码
 * @param newTick 新的Tick数据
 * @param bEmitStrategy 是否触发策略回调
 * @details 处理新的Tick数据，主要功能：
 *          1. 更新价格映射表
 *          2. 检查是否有待处理的信号
 *          3. 处理信号触发和仓位调整
 *          4. 更新动态利润
 *          5. 触发策略的Tick回调
 */
void SelMocker::on_tick(const char* stdCode, WTSTickData* newTick, bool bEmitStrategy /* = true */)
{
	_price_map[stdCode].first = newTick->price();
	_price_map[stdCode].second = (uint64_t)newTick->actiondate() * 1000000000 + newTick->actiontime();

	//先检查是否有信号要触发
	{
		auto it = _sig_map.find(stdCode);
		if (it != _sig_map.end())
		{
			//if (sInfo->isInTradingTime(_replayer->get_raw_time(), true))
			{
				const SigInfo& sInfo = it->second;
				double price;
				if (decimal::eq(sInfo._desprice, 0.0))
					price = newTick->price();
				else
					price = sInfo._desprice;
				do_set_position(stdCode, sInfo._volume, price, sInfo._usertag.c_str(), sInfo._triggered);
				_sig_map.erase(it);
			}

		}
	}

	update_dyn_profit(stdCode, newTick->price());

	if (bEmitStrategy)
		on_tick_updated(stdCode, newTick);
}

/**
 * @brief K线收盘通知
 * @param code 合约代码
 * @param period K线周期
 * @param newBar K线数据
 * @details 将K线收盘事件通知给策略实例
 */
void SelMocker::on_bar_close(const char* code, const char* period, WTSBarStruct* newBar)
{
	if (_strategy)
		_strategy->on_bar(this, code, period, newBar);
}

/**
 * @brief Tick数据更新通知
 * @param code 合约代码
 * @param newTick Tick数据
 * @details 检查是否订阅了该合约的Tick数据，如果有则通知策略
 */
void SelMocker::on_tick_updated(const char* code, WTSTickData* newTick)
{
	auto it = _tick_subs.find(code);
	if (it == _tick_subs.end())
		return;

	if (_strategy)
		_strategy->on_tick(this, code, newTick);
}

/**
 * @brief 策略调度回调
 * @param curDate 当前日期
 * @param curTime 当前时间
 * @details 策略定时调度的回调函数，用于执行策略的定时逻辑
 */
void SelMocker::on_strategy_schedule(uint32_t curDate, uint32_t curTime)
{
	if (_strategy)
		_strategy->on_schedule(this, curDate, curTime);
}


/**
 * @brief 调度回调
 * @param curDate 当前日期
 * @param curTime 当前时间
 * @param fireTime 触发时间
 * @return 是否继续处理
 * @details 系统级别的调度回调，主要完成：
 *          1. 设置调度状态标志，防止重复调度
 *          2. 触发策略调度
 *          3. 检查持仓状态，自动清理无信号的持仓
 *          4. 统计调度次数和执行时间
 */
bool SelMocker::on_schedule(uint32_t curDate, uint32_t curTime, uint32_t fireTime)
{
	_is_in_schedule = true;//开始调度,修改标记

	TimeUtils::Ticker ticker;
	on_strategy_schedule(curDate, curTime);

	faster_hashset<std::string> to_clear;
	for(auto& v : _pos_map)
	{
		const PosInfo& pInfo = v.second;
		const char* code = v.first.c_str();
		if(_sig_map.find(code) == _sig_map.end() && !decimal::eq(pInfo._volume, 0.0))
		{
			//当前信号里没有该持仓,需要清空
			to_clear.insert(code);
		}
	}

	for(const std::string& code : to_clear)
	{
		append_signal(code.c_str(), 0, "autoexit");
	}

	_emit_times++;
	_total_calc_time += ticker.micro_seconds();

	_is_in_schedule = false;//调度结束,修改标记
	return true;
}

/**
 * @brief 枚举持仓信息
 * @param cb 枚举回调函数
 * @details 遍历所有持仓信息和待处理信号，通过回调函数返回最终的目标持仓。
 *          包括当前实际持仓和待执行信号的目标持仓。
 */
void SelMocker::enum_position(FuncEnumSelPositionCallBack cb)
{
	faster_hashmap<std::string, double> desPos;
	for (auto& it : _pos_map)
	{
		const char* stdCode = it.first.c_str();
		const PosInfo& pInfo = it.second;
		desPos[stdCode] = pInfo._volume;
	}

	for (auto sit : _sig_map)
	{
		const char* stdCode = sit.first.c_str();
		const SigInfo& sInfo = sit.second;
		desPos[stdCode] = sInfo._volume;
	}

	for (auto v : desPos)
	{
		cb(v.first.c_str(), v.second);
	}
}

/**
 * @brief 交易时段结束回调
 * @param curTDate 当前交易日期
 * @details 处理交易时段结束事件，主要功能：
 *          1. 计算当日盈亏统计
 *          2. 记录资金变化日志
 *          3. 更新资金信息
 */
void SelMocker::on_session_end(uint32_t curTDate)
{
	uint32_t curDate = curTDate;//_replayer->get_trading_date();

	double total_profit = 0;
	double total_dynprofit = 0;

	for (auto it = _pos_map.begin(); it != _pos_map.end(); it++)
	{
		const char* stdCode = it->first.c_str();
		const PosInfo& pInfo = it->second;
		total_profit += pInfo._closeprofit;
		total_dynprofit += pInfo._dynprofit;
	}


	_fund_logs << StrUtil::printf("%d,%.2f,%.2f,%.2f,%.2f\n", curDate,
		_fund_info._total_profit, _fund_info._total_dynprofit,
		_fund_info._total_profit + _fund_info._total_dynprofit - _fund_info._total_fees, _fund_info._total_fees);

	//save_data();
}

//////////////////////////////////////////////////////////////////////////
//策略接口实现

/**
 * @brief 策略获取价格
 * @param stdCode 标准合约代码
 * @return 当前价格
 * @details 策略接口：获取指定合约的当前价格
 */
double SelMocker::stra_get_price(const char* stdCode)
{
	if (_replayer)
		return _replayer->get_cur_price(stdCode);

	return 0.0;
}

/**
 * @brief 策略设置仓位
 * @param stdCode 标准合约代码
 * @param qty 目标数量
 * @param userTag 用户标签
 * @details 策略接口：设置指定合约的目标仓位。主要功能：
 *          1. 检查合约信息有效性
 *          2. 验证做空权限
 *          3. 检查T+1限制
 *          4. 订阅Tick数据
 *          5. 添加交易信号
 */
void SelMocker::stra_set_position(const char* stdCode, double qty, const char* userTag /* = "" */)
{
	WTSCommodityInfo* commInfo = _replayer->get_commodity_info(stdCode);
	if (commInfo == NULL)
	{
		log_error("Cannot find corresponding commodity info of %s", stdCode);
		return;
	}

	//先检查品种是否可做空，如果目标仓位是负数则不可设置
	if (!commInfo->canShort() && decimal::lt(qty, 0))
	{
		log_error("Cannot short on %s", stdCode);
		return;
	}

	double total = stra_get_position(stdCode, false);
	//如果目标仓位和当前仓位一样的话，直接退出
	if (decimal::eq(total, qty))
		return;

	if (commInfo->isT1())
	{
		double valid = stra_get_position(stdCode, true);
		double frozen = total - valid;
		//如果是T+1限制，目标仓位不能小于冻结仓位
		if (decimal::lt(qty, frozen))
		{
			WTSLogger::log_dyn_f("strategy", _name.c_str(), LL_ERROR, 
				"New position of {} cannot be set to {} due to {} being frozen", stdCode, qty, frozen);
			return;
		}
	}

	_replayer->sub_tick(id(), stdCode);
	append_signal(stdCode, qty, userTag);
}

/**
 * @brief 添加信号
 * @param stdCode 标准合约代码
 * @param qty 目标数量
 * @param userTag 用户标签
 * @param price 指定价格（0表示使用市价）
 * @details 向信号映射表添加新的交易信号，记录目标仓位和用户标签。
 *          信号将在下一个Tick到达时执行。
 */
void SelMocker::append_signal(const char* stdCode, double qty, const char* userTag /* = "" */, double price/* = 0.0*/)
{
	double curPx = _price_map[stdCode].first;

	SigInfo& sInfo = _sig_map[stdCode];
	sInfo._volume = qty;
	sInfo._sigprice = curPx;
	sInfo._desprice = price;
	sInfo._usertag = userTag;
	sInfo._gentime = (uint64_t)_replayer->get_date() * 1000000000 + (uint64_t)_replayer->get_raw_time() * 100000 + _replayer->get_secs();
	sInfo._triggered = !_is_in_schedule;

	log_signal(stdCode, qty, curPx, sInfo._gentime, userTag);

	//save_data();
}

/**
 * @brief 执行设置仓位
 * @param stdCode 标准合约代码
 * @param qty 目标数量
 * @param price 交易价格
 * @param userTag 用户标签
 * @param bTriggered 是否由信号触发
 * @details 实际执行仓位调整逻辑，主要功能：
 *          1. 计算仓位差额和交易方向
 *          2. 应用滑点设置
 *          3. 执行开仓或平仓操作
 *          4. 更新持仓信息和交易记录
 *          5. 计算手续费和盈亏
 *          
 *          支持部分平仓和全部平仓，按FIFO原则处理持仓明细。
 *          对于T+1品种，开仓时会冻结相应仓位。
 */
void SelMocker::do_set_position(const char* stdCode, double qty, double price /* = 0.0 */, const char* userTag /* = "" */, bool bTriggered /* = false */)
{
	PosInfo& pInfo = _pos_map[stdCode];
	double curPx = price;
	if (decimal::eq(price, 0.0))
		curPx = _price_map[stdCode].first;
	uint64_t curTm = (uint64_t)_replayer->get_date() * 10000 + _replayer->get_min_time();
	uint32_t curTDate = _replayer->get_trading_date();

	if (decimal::eq(pInfo._volume, qty))
		return;

	WTSCommodityInfo* commInfo = _replayer->get_commodity_info(stdCode);
	if (commInfo == NULL)
		return;

	//成交价
	double trdPx = curPx;
	double diff = qty - pInfo._volume;
	bool isBuy = decimal::gt(diff, 0.0);
	if (decimal::gt(pInfo._volume*diff, 0))//当前持仓和仓位变动方向一致,增加一条明细,不需要释放资金
	{
		pInfo._volume = qty;

		//如果是T+1，则冻结仓位要增加
		if (commInfo->isT1())
		{
			//ASSERT(diff>0);
			pInfo._frozen += diff;
			log_debug("%s frozen position up to %.0f", stdCode, pInfo._frozen);
		}

		if (_slippage != 0)
		{
			trdPx += _slippage * commInfo->getPriceTick()*(isBuy ? 1 : -1);
		}

		DetailInfo dInfo;
		dInfo._long = decimal::gt(qty, 0);
		dInfo._price = trdPx;
		dInfo._volume = abs(diff);
		dInfo._opentime = curTm;
		dInfo._opentdate = curTDate;
		strcpy(dInfo._opentag, userTag);
		pInfo._details.emplace_back(dInfo);

		double fee = _replayer->calc_fee(stdCode, trdPx, abs(diff), 0);
		_fund_info._total_fees += fee;

		log_trade(stdCode, dInfo._long, true, curTm, trdPx, abs(diff), userTag, fee);
	}
	else
	{//持仓方向和目标仓位不一致,需要平仓
		double left = abs(diff);
		bool isBuy = decimal::gt(diff, 0.0);
		if (_slippage != 0)
			trdPx += _slippage * commInfo->getPriceTick()*(isBuy ? 1 : -1);

		pInfo._volume = qty;
		if (decimal::eq(pInfo._volume, 0))
			pInfo._dynprofit = 0;
		uint32_t count = 0;
		for (auto it = pInfo._details.begin(); it != pInfo._details.end(); it++)
		{
			DetailInfo& dInfo = *it;
			double maxQty = min(dInfo._volume, left);
			if (decimal::eq(maxQty, 0))
				continue;

			double maxProf = dInfo._max_profit * maxQty / dInfo._volume;
			double maxLoss = dInfo._max_loss * maxQty / dInfo._volume;

			dInfo._volume -= maxQty;
			left -= maxQty;

			if (decimal::eq(dInfo._volume, 0))
				count++;

			double profit = (trdPx - dInfo._price) * maxQty * commInfo->getVolScale();
			if (!dInfo._long)
				profit *= -1;

			pInfo._closeprofit += profit;
			pInfo._dynprofit = pInfo._dynprofit*dInfo._volume / (dInfo._volume + maxQty);//浮盈也要同步释放资金
			_fund_info._total_profit += profit;

			double fee = _replayer->calc_fee(stdCode, trdPx, maxQty, dInfo._opentdate == curTDate ? 2 : 1);
			_fund_info._total_fees += fee;
			//这里写成交记录
			log_trade(stdCode, dInfo._long, false, curTm, trdPx, maxQty, userTag, fee);
			//这里写平仓记录
			log_close(stdCode, dInfo._long, dInfo._opentime, dInfo._price, curTm, 
				trdPx, maxQty, profit, pInfo._closeprofit, dInfo._opentag, userTag);

			if (left == 0)
				break;
		}

		//需要清理掉已经平仓完的明细
		while (count > 0)
		{
			auto it = pInfo._details.begin();
			pInfo._details.erase(it);
			count--;
		}

		//最后,如果还有剩余,则需要反手了
		if (left > 0)
		{
			left = left * qty / abs(qty);

			//如果是T+1，则冻结仓位要增加
			if (commInfo->isT1())
			{
				pInfo._frozen += left;
				log_debug("%s frozen position up to %.0f", stdCode, pInfo._frozen);
			}

			DetailInfo dInfo;
			dInfo._long = decimal::gt(qty, 0);
			dInfo._price = trdPx;
			dInfo._volume = abs(left);
			dInfo._opentime = curTm;
			dInfo._opentdate = curTDate;
			strcpy(dInfo._opentag, userTag);
			pInfo._details.emplace_back(dInfo);

			//这里还需要写一笔成交记录
			double fee = _replayer->calc_fee(stdCode, trdPx, abs(left), 0);
			_fund_info._total_fees += fee;
			log_trade(stdCode, dInfo._long, true, curTm, trdPx, abs(left), userTag, fee);
		}
	}
}

/**
 * @brief 策略获取K线数据
 * @param stdCode 标准合约代码
 * @param period K线周期
 * @param count 数据条数
 * @return K线数据切片指针
 * @details 策略接口：获取指定合约的历史K线数据。
 *          支持各种周期的K线数据，包括分钟线、日线等。
 *          自动管理K线标签状态，确保数据完整性。
 */
WTSKlineSlice* SelMocker::stra_get_bars(const char* stdCode, const char* period, uint32_t count)
{
	std::string key = StrUtil::printf("%s#%s", stdCode, period);

	std::string basePeriod = "";
	uint32_t times = 1;
	if (strlen(period) > 1)
	{
		basePeriod.append(period, 1);
		times = strtoul(period + 1, NULL, 10);
		key.append("1");
	}
	else
	{
		basePeriod = period;
		key.append("1");
	}

	WTSKlineSlice* kline = _replayer->get_kline_slice(stdCode, basePeriod.c_str(), count, times, false);

	KlineTag& tag = _kline_tags[key];
	tag._closed = false;

	if (kline)
	{
		double lastClose = kline->at(-1)->close;
		uint64_t lastTime = 0;
		if(basePeriod[0] == 'd')
		{
			lastTime = kline->at(-1)->date;
			WTSSessionInfo* sInfo = _replayer->get_session_info(stdCode, true);
			lastTime *= 1000000000;
			lastTime += (uint64_t)sInfo->getCloseTime() * 100000;
		}
		else
		{
			lastTime = kline->at(-1)->time;
			lastTime += 199000000000;
			lastTime *= 100000;
		}

		if(lastTime > _price_map[stdCode].second)
		{
			_price_map[stdCode].second = lastTime;
			_price_map[stdCode].first = lastClose;
		}
	}

	return kline;
}

/**
 * @brief 策略获取Tick数据
 * @param stdCode 标准合约代码
 * @param count 数据条数
 * @return Tick数据切片指针
 * @details 策略接口：获取指定合约的历史Tick数据。
 *          数据来源于历史数据回放器，用于策略的技术分析。
 */
WTSTickSlice* SelMocker::stra_get_ticks(const char* stdCode, uint32_t count)
{
	return _replayer->get_tick_slice(stdCode, count);
}

/**
 * @brief 策略获取持仓
 * @param stdCode 标准合约代码
 * @param bOnlyValid 是否只返回有效持仓
 * @param userTag 用户标签过滤
 * @return 持仓数量
 * @details 策略接口：获取指定合约的当前持仓数量。
 *          支持按用户标签过滤持仓，正数表示多头，负数表示空头。
 *          对于T+1品种，可以选择是否排除冻结持仓。
 */
double SelMocker::stra_get_position(const char* stdCode, bool bOnlyValid/* = false*/, const char* userTag /* = "" */)
{
	auto it = _pos_map.find(stdCode);
	if (it == _pos_map.end())
		return 0;

	const PosInfo& pInfo = it->second;
	if (strlen(userTag) == 0)
	{
		//只有当userTag为空的时候时候，才会考虑bOnlyValid
		if (bOnlyValid)
		{
			//这里需要注意，只有多头才会冻结库存
			//而且空方要保证可用多头持仓的话，_frozen要为0
			return pInfo._volume - pInfo._frozen;
		}
		else
			return pInfo._volume;
	}

	for (auto it = pInfo._details.begin(); it != pInfo._details.end(); it++)
	{
		const DetailInfo& dInfo = (*it);
		if (strcmp(dInfo._opentag, userTag) != 0)
			continue;

		return dInfo._volume;
	}

	return 0;
}

/**
 * @brief 策略获取最新Tick数据
 * @param stdCode 标准合约代码
 * @return 最新Tick数据指针
 * @details 策略接口：获取指定合约的最新Tick数据
 */
WTSTickData* SelMocker::stra_get_last_tick(const char* stdCode)
{
	return _replayer->get_last_tick(stdCode);
}

/**
 * @brief 策略订阅Tick数据
 * @param code 合约代码
 * @details 策略接口：订阅指定合约的Tick数据。
 *          订阅后该合约的Tick数据会实时推送给策略。
 */
void SelMocker::stra_sub_ticks(const char* code)
{
	/*
	 *	By Wesley @ 2022.03.01
	 *	这里直接向tick订阅列表新增一条
	 *	tick数据回调的时候先检查一下
	 */
	_tick_subs.insert(code);

	_replayer->sub_tick(_context_id, code);
}

/**
 * @brief 策略获取品种信息
 * @param stdCode 标准合约代码
 * @return 品种信息指针
 * @details 策略接口：获取指定合约的品种信息
 */
WTSCommodityInfo* SelMocker::stra_get_comminfo(const char* stdCode)
{
	return _replayer->get_commodity_info(stdCode);
}

/**
 * @brief 策略获取交易时段信息
 * @param stdCode 标准合约代码
 * @return 交易时段信息指针
 * @details 策略接口：获取指定合约的交易时段信息
 */
WTSSessionInfo* SelMocker::stra_get_sessinfo(const char* stdCode)
{
	return _replayer->get_session_info(stdCode, true);
}

/**
 * @brief 策略获取当前日期
 * @return 当前日期
 * @details 策略接口：获取回测当前日期（格式：YYYYMMDD）
 */
uint32_t SelMocker::stra_get_date()
{
	return _replayer->get_date();
}

/**
 * @brief 策略获取当前时间
 * @return 当前时间
 * @details 策略接口：获取回测当前时间（格式：HHMM）
 */
uint32_t SelMocker::stra_get_time()
{
	return _replayer->get_min_time();
}

/**
 * @brief 策略输出信息日志
 * @param message 日志消息
 * @details 策略接口：输出信息级别的日志
 */
void SelMocker::stra_log_info(const char* message)
{
	WTSLogger::log_dyn_raw("strategy", _name.c_str(), LL_INFO, message);
}

/**
 * @brief 策略输出调试日志
 * @param message 日志消息
 * @details 策略接口：输出调试级别的日志
 */
void SelMocker::stra_log_debug(const char* message)
{
	WTSLogger::log_dyn_raw("strategy", _name.c_str(), LL_DEBUG, message);
}

/**
 * @brief 策略输出错误日志
 * @param message 日志消息
 * @details 策略接口：输出错误级别的日志
 */
void SelMocker::stra_log_error(const char* message)
{
	WTSLogger::log_dyn_raw("strategy", _name.c_str(), LL_ERROR, message);
}

/**
 * @brief 策略加载用户数据
 * @param key 数据键
 * @param defVal 默认值
 * @return 数据值
 * @details 策略接口：从内存中加载用户自定义数据
 */
const char* SelMocker::stra_load_user_data(const char* key, const char* defVal /*= ""*/)
{
	auto it = _user_datas.find(key);
	if (it != _user_datas.end())
		return it->second.c_str();

	return defVal;
}

/**
 * @brief 策略保存用户数据
 * @param key 数据键
 * @param val 数据值
 * @details 策略接口：将用户自定义数据保存到内存中
 */
void SelMocker::stra_save_user_data(const char* key, const char* val)
{
	_user_datas[key] = val;
	_ud_modified = true;
}

/**
 * @brief 处理交易时段开始事件
 * @param uCurDate 当前交易日期
 * @details 处理交易时段开始事件，触发策略的on_session_begin()方法
 */
void SelMocker::handle_session_begin(uint32_t uCurDate)
{
	this->on_session_begin(uCurDate);
}
